From: Jasper St. Pierre Date: Thu, 9 May 2013 21:35:57 +0000 (-0400) Subject: popupMenu: Split the remote menu code into its own module X-Git-Tag: archive/raspbian/50.5-1+rpi1~1^2~1013^2^2~4186 X-Git-Url: https://dgit.raspbian.org/%22http://www.example.com/cgi/%22/%22http:/www.example.com/cgi/%22?a=commitdiff_plain;h=5803f6960542ac1456d2498894d85d7bff321d4c;p=gnome-shell.git popupMenu: Split the remote menu code into its own module This is getting sufficiently complicated to deserve its own place. https://bugzilla.gnome.org/show_bug.cgi?id=700257 --- diff --git a/js/Makefile.am b/js/Makefile.am index e8dd927dc..0ff124358 100644 --- a/js/Makefile.am +++ b/js/Makefile.am @@ -77,6 +77,7 @@ nobase_dist_js_DATA = \ ui/pointerWatcher.js \ ui/popupMenu.js \ ui/remoteSearch.js \ + ui/remoteMenu.js \ ui/runDialog.js \ ui/screencast.js \ ui/screenshot.js \ diff --git a/js/ui/panel.js b/js/ui/panel.js index 1aa2d8c9a..ded8ceea2 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -21,6 +21,7 @@ const DND = imports.ui.dnd; const Overview = imports.ui.overview; const PopupMenu = imports.ui.popupMenu; const PanelMenu = imports.ui.panelMenu; +const RemoteMenu = imports.ui.remoteMenu; const Main = imports.ui.main; const Tweener = imports.ui.tweener; @@ -621,11 +622,11 @@ const AppMenuButton = new Lang.Class({ let menu; if (this._targetApp.action_group && this._targetApp.menu) { - if (this.menu instanceof PopupMenu.RemoteMenu && + if (this.menu instanceof RemoteMenu.RemoteMenu && this.menu.actionGroup == this._targetApp.action_group) return; - menu = new PopupMenu.RemoteMenu(this.actor, this._targetApp.menu, this._targetApp.action_group); + menu = new RemoteMenu.RemoteMenu(this.actor, this._targetApp.menu, this._targetApp.action_group); menu.connect('activate', Lang.bind(this, function() { let win = this._targetApp.get_windows()[0]; win.check_alive(global.get_current_time()); diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js index cd81f1079..8a2b06e1a 100644 --- a/js/ui/popupMenu.js +++ b/js/ui/popupMenu.js @@ -1772,205 +1772,6 @@ const PopupComboBoxMenuItem = new Lang.Class({ } }); -/** - * RemoteMenu: - * - * A PopupMenu that tracks a GMenuModel and shows its actions - * (exposed by GApplication/GActionGroup) - */ -const RemoteMenu = new Lang.Class({ - Name: 'RemoteMenu', - Extends: PopupMenu, - - _init: function(sourceActor, model, actionGroup) { - this.parent(sourceActor, 0.0, St.Side.TOP); - - this.model = model; - this.actionGroup = actionGroup; - - this._actions = {}; - this._trackMenu(model, this); - - this._actionStateChangeId = this.actionGroup.connect('action-state-changed', Lang.bind(this, this._actionStateChanged)); - this._actionEnableChangeId = this.actionGroup.connect('action-enabled-changed', Lang.bind(this, this._actionEnabledChanged)); - }, - - destroy: function() { - if (this._actionStateChangeId) { - this.actionGroup.disconnect(this._actionStateChangeId); - this._actionStateChangeId = 0; - } - - if (this._actionEnableChangeId) { - this.actionGroup.disconnect(this._actionEnableChangeId); - this._actionEnableChangeId = 0; - } - - this.parent(); - }, - - _actionAdded: function(model, item, index) { - let action_id = item.action_id; - - if (!this._actions[action_id]) - this._actions[action_id] = { enabled: this.actionGroup.get_action_enabled(action_id), - state: this.actionGroup.get_action_state(action_id), - items: [ ], - }; - let action = this._actions[action_id]; - let target, destroyId, specificSignalId; - - if (action.state) { - // Docs have get_state_hint(), except that the DBus protocol - // has no provision for it (so ShellApp does not implement it, - // and neither GApplication), and g_action_get_state_hint() - // always returns null - // Funny :) - - switch (String.fromCharCode(action.state.classify())) { - case 'b': - action.items.push(item); - item.setOrnament(action.state.get_boolean() ? - Ornament.CHECK : Ornament.NONE); - specificSignalId = item.connect('activate', Lang.bind(this, function(item) { - this.actionGroup.activate_action(action_id, null); - })); - break; - case 's': - action.items.push(item); - item._remoteTarget = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_TARGET, null).deep_unpack(); - item.setOrnament(action.state.deep_unpack() == item._remoteTarget ? - Ornament.DOT : Ornament.NONE); - specificSignalId = item.connect('activate', Lang.bind(this, function(item) { - this.actionGroup.activate_action(action_id, GLib.Variant.new_string(item._remoteTarget)); - })); - break; - default: - log('Action "%s" has state of type %s, which is not supported'.format(action_id, action.state.get_type_string())); - return; - } - } else { - target = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_TARGET, null); - action.items.push(item); - specificSignalId = item.connect('activate', Lang.bind(this, function() { - this.actionGroup.activate_action(action_id, target); - })); - } - - item.actor.reactive = item.actor.can_focus = action.enabled; - - destroyId = item.connect('destroy', Lang.bind(this, function() { - item.disconnect(destroyId); - item.disconnect(specificSignalId); - - let pos = action.items.indexOf(item); - if (pos != -1) - action.items.splice(pos, 1); - })); - }, - - _trackMenu: function(model, item) { - item._tracker = Shell.MenuTracker.new(model, - null, /* action namespace */ - Lang.bind(this, this._insertItem, item), - Lang.bind(this, this._removeItem, item)); - - item.connect('destroy', function() { - item._tracker.destroy(); - item._tracker = null; - }); - }, - - _createMenuItem: function(model, index) { - let labelValue = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_LABEL, null); - let label = labelValue ? labelValue.deep_unpack() : ''; - // remove all underscores that are not followed by another underscore - label = label.replace(/_([^_])/, '$1'); - - let submenuModel = model.get_item_link(index, Gio.MENU_LINK_SUBMENU); - if (submenuModel) { - let item = new PopupSubMenuMenuItem(label); - this._trackMenu(submenuModel, item.menu); - return item; - } - - let item = new PopupMenuItem(label); - let action_id = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_ACTION, null).deep_unpack(); - item.actor.can_focus = item.actor.reactive = false; - - item.action_id = action_id; - - if (this.actionGroup.has_action(action_id)) { - this._actionAdded(model, item, index); - return item; - } - - let signalId = this.actionGroup.connect('action-added', Lang.bind(this, function(actionGroup, actionName) { - actionGroup.disconnect(signalId); - if (this._actions[actionName]) return; - - this._actionAdded(model, item, index); - })); - - return item; - }, - - _actionStateChanged: function(actionGroup, action_id) { - let action = this._actions[action_id]; - if (!action) - return; - - action.state = actionGroup.get_action_state(action_id); - if (action.items.length) { - switch (String.fromCharCode(action.state.classify())) { - case 'b': - for (let i = 0; i < action.items.length; i++) - action.items[i].setOrnament(action.state.get_boolean() ? - Ornament.CHECK : Ornament.NONE); - break; - case 'd': - for (let i = 0; i < action.items.length; i++) - action.items[i].setValue(action.state.get_double()); - break; - case 's': - for (let i = 0; i < action.items.length; i++) - action.items[i].setOrnament(action.items[i]._remoteTarget == action.state.deep_unpack() ? - Ornament.DOT : Ornament.NONE); - } - } - }, - - _actionEnabledChanged: function(actionGroup, action_id) { - let action = this._actions[action_id]; - if (!action) - return; - - action.enabled = actionGroup.get_action_enabled(action_id); - if (action.items.length) { - for (let i = 0; i < action.items.length; i++) { - let item = action.items[i]; - item.actor.reactive = item.actor.can_focus = action.enabled; - } - } - }, - - _insertItem: function(position, model, item_index, action_namespace, is_separator, target) { - let item; - - if (is_separator) - item = new PopupSeparatorMenuItem(); - else - item = this._createMenuItem(model, item_index); - - target.addMenuItem(item, position); - }, - - _removeItem: function(position, target) { - let items = target._getMenuItems(); - items[position].destroy(); - }, -}); - /* Basic implementation of a menu manager. * Call addMenu to add menus */ diff --git a/js/ui/remoteMenu.js b/js/ui/remoteMenu.js new file mode 100644 index 000000000..89157bfb5 --- /dev/null +++ b/js/ui/remoteMenu.js @@ -0,0 +1,210 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +const GLib = imports.gi.GLib; +const Gio = imports.gi.Gio; +const Lang = imports.lang; +const Shell = imports.gi.Shell; +const St = imports.gi.St; + +const PopupMenu = imports.ui.popupMenu; + +/** + * RemoteMenu: + * + * A PopupMenu that tracks a GMenuModel and shows its actions + * (exposed by GApplication/GActionGroup) + */ +const RemoteMenu = new Lang.Class({ + Name: 'RemoteMenu', + Extends: PopupMenu.PopupMenu, + + _init: function(sourceActor, model, actionGroup) { + this.parent(sourceActor, 0.0, St.Side.TOP); + + this.model = model; + this.actionGroup = actionGroup; + + this._actions = {}; + this._trackMenu(model, this); + + this._actionStateChangeId = this.actionGroup.connect('action-state-changed', Lang.bind(this, this._actionStateChanged)); + this._actionEnableChangeId = this.actionGroup.connect('action-enabled-changed', Lang.bind(this, this._actionEnabledChanged)); + }, + + destroy: function() { + if (this._actionStateChangeId) { + this.actionGroup.disconnect(this._actionStateChangeId); + this._actionStateChangeId = 0; + } + + if (this._actionEnableChangeId) { + this.actionGroup.disconnect(this._actionEnableChangeId); + this._actionEnableChangeId = 0; + } + + this.parent(); + }, + + _actionAdded: function(model, item, index) { + let action_id = item.action_id; + + if (!this._actions[action_id]) + this._actions[action_id] = { enabled: this.actionGroup.get_action_enabled(action_id), + state: this.actionGroup.get_action_state(action_id), + items: [ ], + }; + let action = this._actions[action_id]; + let target, destroyId, specificSignalId; + + if (action.state) { + // Docs have get_state_hint(), except that the DBus protocol + // has no provision for it (so ShellApp does not implement it, + // and neither GApplication), and g_action_get_state_hint() + // always returns null + // Funny :) + + switch (String.fromCharCode(action.state.classify())) { + case 'b': + action.items.push(item); + item.setOrnament(action.state.get_boolean() ? + PopupMenu.Ornament.CHECK : + PopupMenu.Ornament.NONE); + specificSignalId = item.connect('activate', Lang.bind(this, function(item) { + this.actionGroup.activate_action(action_id, null); + })); + break; + case 's': + action.items.push(item); + item._remoteTarget = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_TARGET, null).deep_unpack(); + item.setOrnament(action.state.deep_unpack() == item._remoteTarget ? + PopupMenu.Ornament.DOT : + PopupMenu.Ornament.NONE); + specificSignalId = item.connect('activate', Lang.bind(this, function(item) { + this.actionGroup.activate_action(action_id, GLib.Variant.new_string(item._remoteTarget)); + })); + break; + default: + log('Action "%s" has state of type %s, which is not supported'.format(action_id, action.state.get_type_string())); + return; + } + } else { + target = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_TARGET, null); + action.items.push(item); + specificSignalId = item.connect('activate', Lang.bind(this, function() { + this.actionGroup.activate_action(action_id, target); + })); + } + + item.actor.reactive = item.actor.can_focus = action.enabled; + + destroyId = item.connect('destroy', Lang.bind(this, function() { + item.disconnect(destroyId); + item.disconnect(specificSignalId); + + let pos = action.items.indexOf(item); + if (pos != -1) + action.items.splice(pos, 1); + })); + }, + + _trackMenu: function(model, item) { + item._tracker = Shell.MenuTracker.new(model, + null, /* action namespace */ + Lang.bind(this, this._insertItem, item), + Lang.bind(this, this._removeItem, item)); + + item.connect('destroy', function() { + item._tracker.destroy(); + item._tracker = null; + }); + }, + + _createMenuItem: function(model, index) { + let labelValue = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_LABEL, null); + let label = labelValue ? labelValue.deep_unpack() : ''; + // remove all underscores that are not followed by another underscore + label = label.replace(/_([^_])/, '$1'); + + let submenuModel = model.get_item_link(index, Gio.MENU_LINK_SUBMENU); + if (submenuModel) { + let item = new PopupMenu.PopupSubMenuMenuItem(label); + this._trackMenu(submenuModel, item.menu); + return item; + } + + let item = new PopupMenu.PopupMenuItem(label); + let action_id = model.get_item_attribute_value(index, Gio.MENU_ATTRIBUTE_ACTION, null).deep_unpack(); + item.actor.can_focus = item.actor.reactive = false; + + item.action_id = action_id; + + if (this.actionGroup.has_action(action_id)) { + this._actionAdded(model, item, index); + return item; + } + + let signalId = this.actionGroup.connect('action-added', Lang.bind(this, function(actionGroup, actionName) { + actionGroup.disconnect(signalId); + if (this._actions[actionName]) return; + + this._actionAdded(model, item, index); + })); + + return item; + }, + + _actionStateChanged: function(actionGroup, action_id) { + let action = this._actions[action_id]; + if (!action) + return; + + action.state = actionGroup.get_action_state(action_id); + if (action.items.length) { + switch (String.fromCharCode(action.state.classify())) { + case 'b': + for (let i = 0; i < action.items.length; i++) + action.items[i].setOrnament(action.state.get_boolean() ? + Ornament.CHECK : Ornament.NONE); + break; + case 'd': + for (let i = 0; i < action.items.length; i++) + action.items[i].setValue(action.state.get_double()); + break; + case 's': + for (let i = 0; i < action.items.length; i++) + action.items[i].setOrnament(action.items[i]._remoteTarget == action.state.deep_unpack() ? + Ornament.DOT : Ornament.NONE); + } + } + }, + + _actionEnabledChanged: function(actionGroup, action_id) { + let action = this._actions[action_id]; + if (!action) + return; + + action.enabled = actionGroup.get_action_enabled(action_id); + if (action.items.length) { + for (let i = 0; i < action.items.length; i++) { + let item = action.items[i]; + item.actor.reactive = item.actor.can_focus = action.enabled; + } + } + }, + + _insertItem: function(position, model, item_index, action_namespace, is_separator, target) { + let item; + + if (is_separator) + item = new PopupMenu.PopupSeparatorMenuItem(); + else + item = this._createMenuItem(model, item_index); + + target.addMenuItem(item, position); + }, + + _removeItem: function(position, target) { + let items = target._getMenuItems(); + items[position].destroy(); + }, +});